unit DIrr.Rect;

{$I DIrr.inc}

interface

uses
  Math, DIrr.Math, DIrr.Vector2d, DIrr.Dimension2d;

type
  PIrrRectF = ^TIrrRectF;
  TIrrRectF = record

  public
    //! move right by given numbers
    class operator Add(Value1: TIrrRectF; Value2: TVector2DF): TIrrRectF;

    //! move left by given numbers
    class operator Subtract(Value1: TIrrRectF; Value2: TVector2DF): TIrrRectF;

    //! equality operator
    class operator Equal(Value1, Value2: TIrrRectF): Boolean;

    //! inequality operator
    class operator NotEqual(Value1, Value2: TIrrRectF): Boolean;

    //! compares size of rectangles
    class operator LessThan(Value1, Value2: TIrrRectF): Boolean;
    class operator LessThanOrEqual(Value1, Value2: TIrrRectF): Boolean;
    class operator GreaterThan(Value1, Value2: TIrrRectF): Boolean;
    class operator GreaterThanOrEqual(Value1, Value2: TIrrRectF): Boolean;

    procedure Init(x, y, x2, y2: single); inline;
    //! Returns size of rectangle
    function GetArea: Single;

		//! Returns if a 2d point is within this rectangle.
		(* \param pos Position to test if it lies within this rectangle.
		\return True if the position is within the rectangle, false if not. *)
    function IsPointInside(const Value: TVector2DF): Boolean;

		//! Check if the rectangle collides with another rectangle.
		(* \param other Rectangle to test collision with
		\return True if the rectangles collide. *)
    function IsRectCollided(const Other: TIrrRectF): Boolean;

		//! Clips this rectangle with another one.
		(* \param other Rectangle to clip with *)
    procedure ClipAgainst(const Other: TIrrRectF);

		//! Moves this rectangle to fit inside another one.
		(* \return True on success, false if not possible *)
    function ConstraintTo(const Other: TIrrRectF): Boolean;

    function GetWidth: Single;
    function GetHeight: Single;

    //! If the lower right corner of the rect is smaller then the upper left, the points are swapped.
    procedure Repair;

		//! Returns if the rect is valid to draw.
		(* It would be invalid if the UpperLeftCorner is lower or more
		right than the LowerRightCorner. *)
    function IsValid: Boolean;

    //! Get the center of the rectangle
    function GetCenter: TVector2DF;

    //! Get the dimensions of the rectangle
    function GetSize: TDimension2DF;

		//! Adds a point to the rectangle
		(* Causes the rectangle to grow bigger if point is outside of
		the box
		\param p Point to add to the box. *)
    procedure AddInternalPoint(const P: TVector2DF);
  public
    case Boolean of
    True:(
      UpperLeftCorner: TVector2DF;
      LowerRightCorner: TVector2DF;);
    False:
      (
      Left, Top, Right, Bottom: Single;
      );

  end;

type
  TIrrRectArray = array of Integer;

  PIrrRect = ^TIrrRect;
  TIrrRect = record

  public
    procedure Init(x, y, x2, y2: Integer); overload; inline;
    procedure Init(const UpperLeft, LowerRight: TVector2D); overload; inline;
    constructor Create(x, y, x2, y2: Integer);

    //! move right by given numbers
    class operator Add(Value1: TIrrRect; Value2: TVector2D): TIrrRect;

    //! move left by given numbers
    class operator Subtract(Value1: TIrrRect; Value2: TVector2D): TIrrRect;

    //! equality operator
    class operator Equal(Value1, Value2: TIrrRect): Boolean;

    //! inequality operator
    class operator NotEqual(Value1, Value2: TIrrRect): Boolean;

    //! compares size of rectangles
    class operator LessThan(Value1, Value2: TIrrRect): Boolean;
    class operator LessThanOrEqual(Value1, Value2: TIrrRect): Boolean;
    class operator GreaterThan(Value1, Value2: TIrrRect): Boolean;
    class operator GreaterThanOrEqual(Value1, Value2: TIrrRect): Boolean;

    class operator Implicit(const Value: TIrrRectArray): TIrrRect;

    //! Returns size of rectangle
    function GetArea: Single;

		//! Returns if a 2d point is within this rectangle.
		(* \param pos Position to test if it lies within this rectangle.
		\return True if the position is within the rectangle, false if not. *)
    function IsPointInside(const Value: TVector2D): Boolean;

		//! Check if the rectangle collides with another rectangle.
		(* \param other Rectangle to test collision with
		\return True if the rectangles collide. *)
    function IsRectCollided(const Other: TIrrRect): Boolean;

		//! Clips this rectangle with another one.
		(* \param other Rectangle to clip with *)
    procedure ClipAgainst(const Other: TIrrRect);

		//! Moves this rectangle to fit inside another one.
		(* \return True on success, false if not possible *)
    function ConstraintTo(const Other: TIrrRect): Boolean;

    function GetWidth: Integer;
    function GetHeight: Integer;

    //! If the lower right corner of the rect is smaller then the upper left, the points are swapped.
    procedure Repair;

		//! Returns if the rect is valid to draw.
		(* It would be invalid if the UpperLeftCorner is lower or more
		right than the LowerRightCorner. *)
    function IsValid: Boolean;

    //! Get the center of the rectangle
    function GetCenter: TVector2D;

    //! Get the dimensions of the rectangle
    function GetSize: TDimension2D;

		//! Adds a point to the rectangle
		(* Causes the rectangle to grow bigger if point is outside of
		the box
		\param p Point to add to the box. *)
    procedure AddInternalPoint(const P: TVector2D);
  public
    case Boolean of
    False:
      (UpperLeftCorner: TVector2D;
       LowerRightCorner: TVector2D;);
    True:
      (
      Left, Top, Right, Bottom: Integer;
      );
  end;

  function IrrRect(x, y, x2, y2: Integer): TIrrRect; {$IFDEF INLINE} inline; {$ENDIF}
  function IrrRectF(x, y, x2, y2: Single): TIrrRectF; {$IFDEF INLINE} inline; {$ENDIF}
implementation

function IrrRect(x, y, x2, y2: Integer): TIrrRect;
begin
  Result.Init(x, y, x2, y2);
end;

function IrrRectF(x, y, x2, y2: Single): TIrrRectF; {$IFDEF INLINE} inline; {$ENDIF}
begin
  Result.Init(x, y, x2, y2);
end;

{ TIrrRectF }

class operator TIrrRectF.Add(Value1: TIrrRectF; Value2: TVector2DF): TIrrRectF;
begin
  Result.UpperLeftCorner := Value1.UpperLeftCorner + Value2;
  Result.LowerRightCorner := Value1.LowerRightCorner + Value2;
end;

procedure TIrrRectF.AddInternalPoint(const P: TVector2DF);
begin
  if P.X > LowerRightCorner.X then
    LowerRightCorner.X := P.X;
  if P.Y > LowerRightCorner.Y then
    LowerRightCorner.Y := P.Y;

  if P.X < UpperLeftCorner.X then
    UpperLeftCorner.X := P.X;
  if P.Y < UpperLeftCorner.Y then
    UpperLeftCorner.Y := P.Y;
end;

procedure TIrrRectF.ClipAgainst(const Other: TIrrRectF);
begin
  if Other.LowerRightCorner.X < LowerRightCorner.X then
    LowerRightCorner.X := Other.LowerRightCorner.X;
  if Other.LowerRightCorner.Y < LowerRightCorner.Y then
    LowerRightCorner.Y := Other.LowerRightCorner.Y;

  if Other.UpperLeftCorner.X > UpperLeftCorner.X then
    UpperLeftCorner.X := Other.UpperLeftCorner.X;
  if Other.UpperLeftCorner.Y > UpperLeftCorner.Y then
    UpperLeftCorner.Y := Other.UpperLeftCorner.Y;

  // correct possible invalid rect resulting from clipping
  if UpperLeftCorner.Y > LowerRightCorner.Y then
    UpperLeftCorner.Y := LowerRightCorner.Y;
  if UpperLeftCorner.X < LowerRightCorner.X then
    UpperLeftCorner.X := LowerRightCorner.X;
end;

function TIrrRectF.ConstraintTo(const Other: TIrrRectF): Boolean;
var
  diff: Single;
begin
  if (Other.GetWidth >= GetWidth) and (other.GetHeight >= GetHeight) then
  begin
    diff := Other.LowerRightCorner.X - LowerRightCorner.X;
    if (diff < 0) then
    begin
      LowerRightCorner.X := LowerRightCorner.X + diff;
      UpperLeftCorner.X := UpperLeftCorner.X + diff;
    end;

    diff := Other.LowerRightCorner.Y - LowerRightCorner.Y;
    if (diff < 0) then
    begin
      LowerRightCorner.Y := LowerRightCorner.Y + diff;
      UpperLeftCorner.Y := UpperLeftCorner.Y + diff;
    end;

    diff := UpperLeftCorner.X - Other.UpperLeftCorner.X;
    if (diff < 0) then
    begin
      UpperLeftCorner.X := UpperLeftCorner.X - diff;
      LowerRightCorner.X := LowerRightCorner.X - diff;
    end;

    diff := UpperLeftCorner.Y - Other.UpperLeftCorner.Y;
    if (diff < 0) then
    begin
      UpperLeftCorner.Y := UpperLeftCorner.Y - diff;
      LowerRightCorner.Y := LowerRightCorner.Y - diff;
    end;

    Result := True;
  end
  else
  begin
    Result := False;
  end;
end;

class operator TIrrRectF.Equal(Value1, Value2: TIrrRectF): Boolean;
begin
  Result := (Value1.UpperLeftCorner = Value2.UpperLeftCorner) and
    (Value1.LowerRightCorner = Value2.LowerRightCorner);
end;

function TIrrRectF.GetArea: Single;
begin
  Result := GetWidth * GetHeight;
end;

function TIrrRectF.GetCenter: TVector2DF;
begin
  Result.X := (UpperLeftCorner.X + LowerRightCorner.X) / 2;
  Result.Y := (UpperLeftCorner.Y + LowerRightCorner.Y) / 2;
end;

function TIrrRectF.GetHeight: Single;
begin
  Result := LowerRightCorner.Y - UpperLeftCorner.Y;
end;

function TIrrRectF.GetSize: TDimension2DF;
begin
  Result.Width := GetWidth;
  Result.Height := GetHeight;
end;

function TIrrRectF.GetWidth: Single;
begin
  Result := LowerRightCorner.X - UpperLeftCorner.X;
end;

class operator TIrrRectF.GreaterThan(Value1, Value2: TIrrRectF): Boolean;
begin
  Result := Value1.GetArea > Value2.GetArea;
end;

class operator TIrrRectF.GreaterThanOrEqual(Value1, Value2: TIrrRectF): Boolean;
begin
  Result := Value1.GetArea >= Value2.GetArea;
end;

procedure TIrrRectF.Init(x, y, x2, y2: single);
begin
  UpperLeftCorner.X := x;
  UpperLeftCorner.Y := y;
  LowerRightCorner.X := x2;
  LowerRightCorner.Y := y2;
end;

function TIrrRectF.IsPointInside(const Value: TVector2DF): Boolean;
begin
  Result := (UpperLeftCorner.X <= Value.X) and
    (UpperLeftCorner.Y <= Value.Y) and
    (LowerRightCorner.X >= Value.X) and
    (LowerRightCorner.Y >= Value.Y);
end;

function TIrrRectF.IsRectCollided(const Other: TIrrRectF): Boolean;
begin
  Result := (LowerRightCorner.Y > Other.UpperLeftCorner.Y) and
    (UpperLeftCorner.Y < Other.LowerRightCorner.Y) and
    (LowerRightCorner.X > Other.UpperLeftCorner.X) and
    (UpperLeftCorner.X < Other.LowerRightCorner.X);
end;

function TIrrRectF.IsValid: Boolean;
begin
  Result := (LowerRightCorner.X >= UpperLeftCorner.X) and
    (LowerRightCorner.Y >= UpperLeftCorner.Y);
end;

class operator TIrrRectF.LessThan(Value1, Value2: TIrrRectF): Boolean;
begin
  Result := Value1.GetArea < Value2.GetArea;
end;

class operator TIrrRectF.LessThanOrEqual(Value1, Value2: TIrrRectF): Boolean;
begin
  Result := Value1.GetArea <= Value2.GetArea;
end;

class operator TIrrRectF.NotEqual(Value1, Value2: TIrrRectF): Boolean;
begin
  Result := (Value1.UpperLeftCorner <> Value2.UpperLeftCorner) or
    (Value1.LowerRightCorner <> Value2.LowerRightCorner);
end;

procedure TIrrRectF.Repair;
var
  t: Single;
begin
  if (LowerRightCorner.X < UpperLeftCorner.X) then
  begin
    t := LowerRightCorner.X;
    LowerRightCorner.X := UpperLeftCorner.X;
    UpperLeftCorner.X := t;
  end;

  if (LowerRightCorner.Y < UpperLeftCorner.Y) then
  begin
    t := LowerRightCorner.Y;
    LowerRightCorner.Y := UpperLeftCorner.Y;
    UpperLeftCorner.Y := t;
  end;
end;

class operator TIrrRectF.Subtract(Value1: TIrrRectF;
  Value2: TVector2DF): TIrrRectF;
begin
  Result.UpperLeftCorner := Value1.UpperLeftCorner - Value2;
  Result.LowerRightCorner := Value1.LowerRightCorner - Value2;
end;

{ TIrrRect }

class operator TIrrRect.Add(Value1: TIrrRect; Value2: TVector2D): TIrrRect;
begin
  Result.UpperLeftCorner := Value1.UpperLeftCorner + Value2;
  Result.LowerRightCorner := Value1.LowerRightCorner + Value2;
end;

procedure TIrrRect.AddInternalPoint(const P: TVector2D);
begin
  if P.X > LowerRightCorner.X then
    LowerRightCorner.X := P.X;
  if P.Y > LowerRightCorner.Y then
    LowerRightCorner.Y := P.Y;

  if P.X < UpperLeftCorner.X then
    UpperLeftCorner.X := P.X;
  if P.Y < UpperLeftCorner.Y then
    UpperLeftCorner.Y := P.Y;
end;

procedure TIrrRect.ClipAgainst(const Other: TIrrRect);
begin
  if Other.LowerRightCorner.X < LowerRightCorner.X then
    LowerRightCorner.X := Other.LowerRightCorner.X;
  if Other.LowerRightCorner.Y < LowerRightCorner.Y then
    LowerRightCorner.Y := Other.LowerRightCorner.Y;

  if Other.UpperLeftCorner.X > UpperLeftCorner.X then
    UpperLeftCorner.X := Other.UpperLeftCorner.X;
  if Other.UpperLeftCorner.Y > UpperLeftCorner.Y then
    UpperLeftCorner.Y := Other.UpperLeftCorner.Y;

  // correct possible invalid rect resulting from clipping
  if UpperLeftCorner.Y > LowerRightCorner.Y then
    UpperLeftCorner.Y := LowerRightCorner.Y;
  if UpperLeftCorner.X < LowerRightCorner.X then
    UpperLeftCorner.X := LowerRightCorner.X;
end;

function TIrrRect.ConstraintTo(const Other: TIrrRect): Boolean;
var
  diff: Integer;
begin
  if (Other.GetWidth >= GetWidth) and (other.GetHeight >= GetHeight) then
  begin
    diff := Other.LowerRightCorner.X - LowerRightCorner.X;
    if (diff < 0) then
    begin
      LowerRightCorner.X := LowerRightCorner.X + diff;
      UpperLeftCorner.X := UpperLeftCorner.X + diff;
    end;

    diff := Other.LowerRightCorner.Y - LowerRightCorner.Y;
    if (diff < 0) then
    begin
      LowerRightCorner.Y := LowerRightCorner.Y + diff;
      UpperLeftCorner.Y := UpperLeftCorner.Y + diff;
    end;

    diff := UpperLeftCorner.X - Other.UpperLeftCorner.X;
    if (diff < 0) then
    begin
      UpperLeftCorner.X := UpperLeftCorner.X - diff;
      LowerRightCorner.X := LowerRightCorner.X - diff;
    end;

    diff := UpperLeftCorner.Y - Other.UpperLeftCorner.Y;
    if (diff < 0) then
    begin
      UpperLeftCorner.Y := UpperLeftCorner.Y - diff;
      LowerRightCorner.Y := LowerRightCorner.Y - diff;
    end;

    Result := True;
  end
  else
  begin
    Result := False;
  end;
end;

constructor TIrrRect.Create(x, y, x2, y2: Integer);
begin
  Init(x, y, x2, y2);
end;

procedure TIrrRect.Init(x, y, x2, y2: Integer);
begin
  UpperLeftCorner.X := x;
  UpperLeftCorner.Y := y;
  LowerRightCorner.X := x2;
  LowerRightCorner.Y := y2;
end;

procedure TIrrRect.Init(const UpperLeft, LowerRight: TVector2D);
begin
  UpperLeftCorner := UpperLeft;
  LowerRightCorner := LowerRight;
end;

class operator TIrrRect.Equal(Value1, Value2: TIrrRect): Boolean;
begin
  Result := (Value1.UpperLeftCorner = Value2.UpperLeftCorner) and
    (Value1.LowerRightCorner = Value2.LowerRightCorner);
end;

function TIrrRect.GetArea: Single;
begin
  Result := GetWidth * GetHeight;
end;

function TIrrRect.GetCenter: TVector2D;
begin
  Result.X := (UpperLeftCorner.X + LowerRightCorner.X) div 2;
  Result.Y := (UpperLeftCorner.Y + LowerRightCorner.Y) div 2;
end;

function TIrrRect.GetHeight: Integer;
begin
  Result := LowerRightCorner.Y - UpperLeftCorner.Y;
end;

function TIrrRect.GetSize: TDimension2D;
begin
  Result.Width := GetWidth;
  Result.Height := GetHeight;
end;

function TIrrRect.GetWidth: Integer;
begin
  Result := LowerRightCorner.X - UpperLeftCorner.X;
end;

class operator TIrrRect.GreaterThan(Value1, Value2: TIrrRect): Boolean;
begin
  Result := Value1.GetArea > Value2.GetArea;
end;

class operator TIrrRect.GreaterThanOrEqual(Value1, Value2: TIrrRect): Boolean;
begin
  Result := Value1.GetArea >= Value2.GetArea;
end;

class operator TIrrRect.Implicit(const Value: TIrrRectArray): TIrrRect;
begin
  FilLChar(Result, SizeOf(Result), 0);

  if High(Value) - Low(Value) + 1 >= 4 then
  begin
    Result.UpperLeftCorner.X := Value[0];
    Result.UpperLeftCorner.Y := Value[1];
    Result.LowerRightCorner.X := Value[2];
    Result.LowerRightCorner.Y := Value[3];
  end;
end;

function TIrrRect.IsPointInside(const Value: TVector2D): Boolean;
begin
  Result := (UpperLeftCorner.X <= Value.X) and
    (UpperLeftCorner.Y <= Value.Y) and
    (LowerRightCorner.X >= Value.X) and
    (LowerRightCorner.Y >= Value.Y);
end;

function TIrrRect.IsRectCollided(const Other: TIrrRect): Boolean;
begin
  Result := (LowerRightCorner.Y > Other.UpperLeftCorner.Y) and
    (UpperLeftCorner.Y < Other.LowerRightCorner.Y) and
    (LowerRightCorner.X > Other.UpperLeftCorner.X) and
    (UpperLeftCorner.X < Other.LowerRightCorner.X);
end;

function TIrrRect.IsValid: Boolean;
begin
  Result := (LowerRightCorner.X >= UpperLeftCorner.X) and
    (LowerRightCorner.Y >= UpperLeftCorner.Y);
end;

class operator TIrrRect.LessThan(Value1, Value2: TIrrRect): Boolean;
begin
  Result := Value1.GetArea < Value2.GetArea;
end;

class operator TIrrRect.LessThanOrEqual(Value1, Value2: TIrrRect): Boolean;
begin
  Result := Value1.GetArea <= Value2.GetArea;
end;

class operator TIrrRect.NotEqual(Value1, Value2: TIrrRect): Boolean;
begin
  Result := (Value1.UpperLeftCorner <> Value2.UpperLeftCorner) or
    (Value1.LowerRightCorner <> Value2.LowerRightCorner);
end;

procedure TIrrRect.Repair;
var
  t: Integer;
begin
  if (LowerRightCorner.X < UpperLeftCorner.X) then
  begin
    t := LowerRightCorner.X;
    LowerRightCorner.X := UpperLeftCorner.X;
    UpperLeftCorner.X := t;
  end;

  if (LowerRightCorner.Y < UpperLeftCorner.Y) then
  begin
    t := LowerRightCorner.Y;
    LowerRightCorner.Y := UpperLeftCorner.Y;
    UpperLeftCorner.Y := t;
  end;
end;

class operator TIrrRect.Subtract(Value1: TIrrRect;
  Value2: TVector2D): TIrrRect;
begin
  Result.UpperLeftCorner := Value1.UpperLeftCorner - Value2;
  Result.LowerRightCorner := Value1.LowerRightCorner - Value2;
end;


end.